package ua.pp.bizon.cripto.utils;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;

import ua.pp.bizon.cripto.file.IFile;
import ua.pp.bizon.cripto.file.IPath;
import ua.pp.bizon.cripto.file.Path;

public class FileUtilFtpImpl extends Path implements IFile, IPath {

    public FileUtilFtpImpl(String path, String username, String password, String host) {
        super(Place.FTP, path, username, password, host);
    }

    private static Log log = LogFactory.getLog(FileUtilFtpImpl.class);

    public byte[] read() throws IOException {
        FTPClient client = getClient();
        client.setFileType(FTPClient.BINARY_FILE_TYPE);
        String[] pathes = this.getPath().replaceAll("\\\\", "/").split("/");
        String filename = pathes[pathes.length - 1];
        cwdFtpTo(client, true);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        client.retrieveFile(filename, outputStream);
        outputStream.close();
        byte buffer[] = outputStream.toByteArray();
        client.disconnect();
        log.debug("read ftp done");
        return buffer;
    }

    protected FTPClient getClient() throws IOException {
        FTPClient client = new FTPClient();
        log.debug("connect to host " + getHost());
        client.connect(getHost());
        log.debug("login using username " + getUsername());
        if (!client.login(getUsername(), getPassword())){
            throw new IOException("user is not login, code " + client.getReplyCode() + ", message: " + client.getReplyString());
        }
        return client;
    }

    public void write(String filename, byte[] data) throws IOException {
        log.debug("FTP WRITE: to = " + this + ", filename = " + filename + ", data.length = " + data.length);
        FTPClient client = getClient();
        client.setFileType(FTPClient.BINARY_FILE_TYPE);
        cwdFtpTo(client, true);
        if (filename.isEmpty()) {
            filename = getPath().substring(getPath().lastIndexOf('/') + 1);
            log.debug("FTP WRITE: new filename = '" + filename + "'");
        }
        log.debug("rescheck if file exist");
        int size = 0;
        FTPFile[] files = client.listFiles();
        for (FTPFile i : files) {
            if (i.getName().equals(filename)) {
                log.debug("File " + i.getName() + " exist, appending");
                if (i.getSize() > Integer.MAX_VALUE) {
                    log.error("Sorry, we not support files bigger than " + Integer.MAX_VALUE + " bytes");
                    throw new RuntimeException("Sorry, we not support files bigger than " + Integer.MAX_VALUE + " bytes");
                }
                size = (int) i.getSize();
            }
        }
        log.debug("FTP WRITE STARTED");
        long start = System.currentTimeMillis();
        log.trace("clientappendfile=" + client.appendFile(filename, new ByteArrayInputStream(data, size, data.length - size)));
        log.trace("client responce code:" + client.getReplyCode() + ", string:" + client.getReplyString());
        long end = System.currentTimeMillis();
        log.debug("FTP WRITE FINISHED, time = " + (end - start) + ", speed: " + ((data.length * 1000) / (128 * (end - start))) + "kbps");
        if (client.getReplyCode() != 226) {
            log.error("client error code:" + client.getReplyCode() + ", string:" + client.getReplyString());
            throw new IOException(client.getReplyString());
        }
        client.disconnect();
        log.trace("save to ftp done");
    }

    /**
     * go to directory where file should be append. <br>
     * if to.getPath() end with / - it is directory. if not - last part of name
     * is filename.
     * 
     * @param to
     * @param client
     * @param create
     * @param name
     * @return
     * @throws IOException
     */
    protected boolean cwdFtpTo(FTPClient client, boolean create) throws IOException {
        String normalizedTo = getPath().replaceAll("\\\\", "/");
        String[] pathes = normalizedTo.split("/");
        // we get filename from origin file
        boolean isDirectory = normalizedTo.charAt(normalizedTo.length() - 1) == '/';
        if (pathes.length > 0) {
            for (int i = 0; isDirectory ? (i < pathes.length) : (i < pathes.length - 1); i++) {
                if (!pathes[i].isEmpty()) {
                    log.trace("Ftp client: we at: " + client.printWorkingDirectory() + ", go to \'" + pathes[i] + "'");
                    int reply = client.cwd(pathes[i]);
                    if (reply > 400) {
                        if (create) {
                            log.info("error cwd to " + pathes[i] + ", code: " + reply + " , message: " + client.getReplyString());
                            log.debug("try creating directory " + pathes[i]);
                            log.trace("create directory is " + client.makeDirectory(pathes[i]) + ", message: " + client.getReplyString());
                            log.trace("cwd is " + client.cwd(pathes[i]) + ", " + client.getReplyString());
                        } else {
                            log.warn("error cwd to " + pathes[i] + ", code: " + reply + " , message: " + client.getReplyString());
                            return false;
                        }
                    }
                    log.trace("cwd is " + reply + ", " + client.getReplyString());
                    log.trace("Ftp client: we at: " + client.printWorkingDirectory());
                }
            }
        }
        return true;
    }

    public boolean exists() throws IOException {
        log.debug("exist  : " + this);
        FTPClient client = getClient();
        String[] pathes = getPath().replaceAll("\\\\", "/").split("/");
        boolean find = false;
        if (pathes.length > 0) {
            for (int i = 0; i < pathes.length - 1; i++){
                log.trace("Ftp client: we at: " + client.printWorkingDirectory() + ", go to \'" + pathes[i] + "'");
                int reply = client.cwd(pathes[i]);
                if (reply >= 400) {
                    log.warn("exist cwd " + pathes[i] + " responce code : " + client.getReplyCode() + ", reply: " + client.getReplyString());
                    return false;
                }
                log.trace("exist responce code : " + client.getReplyCode() + ", reply: " + client.getReplyString());
                
            }
            FTPFile files[] = client.listFiles();
            for (FTPFile f : files) {
                if (f != null) {
                    find |= pathes[pathes.length - 1].equals(f.getName());
                } else {
                    log.warn("exist: listfiles() return null");
                }
            }
        }
        return find;
    }

    public boolean writable() {
        // TODO Auto-generated method stub
        return true;
    }

    public boolean isDirectory() throws IOException {
        String normalizedTo = getPath().replaceAll("\\\\", "/");
        boolean isDirectory = normalizedTo.charAt(normalizedTo.length() - 1) == '/';
        if (isDirectory == true)
            return true;
        FTPClient client = getClient();
        isDirectory = cwdFtpTo(client, false);
        if (isDirectory == false) {
            client.disconnect();
            return isDirectory;
        } else {
            isDirectory = client.changeWorkingDirectory(normalizedTo.split("/")[normalizedTo.split("/").length - 1]);
            client.disconnect();
            return isDirectory;
        }
    }

    public List<IPath> ls() throws IOException {
        FTPClient client = getClient();
        cwdFtpTo(client, true);
        List<IPath> rezult = new LinkedList<IPath>();
        FTPFile[] names = client.listFiles();
        for (FTPFile i : names) {
            rezult.add(new FileUtilFtpImpl(getPath() + i.getName() + (i.isDirectory() ? File.separatorChar : ""), getUsername(), getPassword(), getHost()));
        }
        client.disconnect();
        return rezult;
    }

    public IPath mkdir(String lastPathName) throws IOException {
        log.debug("FTP mkdir: to=" + this + ", lastPathName=" + lastPathName);
        FTPClient client = getClient();
        cwdFtpTo(client, true);
        if (!client.makeDirectory(lastPathName) && client.getReplyCode() != 550) {
            String message = "error during mkdir ,code : " + client.getReplyCode() + ", reply: " + client.getReplyString();
            log.warn(message);
        } else {
            log.debug("mkdir responce code : " + client.getReplyCode() + ", reply: " + client.getReplyString());
        }
        return new FileUtilFtpImpl(getPath() + lastPathName + File.separatorChar, getUsername(), getPassword(), getHost());
    }

    @Override
    public boolean delete() throws IOException {
        log.debug("ftp delete file " + getPath());
        boolean res = false;
        FTPClient client = getClient();
        if (isDirectory()) {
            for (IPath i : ls()) {
                res |= !i.delete();
            }
            res |= client.deleteFile(getPath());
            log.debug("ftp delete file " + getPath() + " reply code: " + client.getReplyCode() + ", reply string: " + client.getReplyString());
            client.disconnect();
            return !res;
        } else {
            try {
                return client.deleteFile(getPath());
            } finally {
                log.debug("ftp delete file " + getPath() + " reply code: " + client.getReplyCode() + ", reply string: " + client.getReplyString());
                client.disconnect();
            }
        }

    }
}
